﻿using System;
using System.Net;
using System.Net.Sockets.Kcp;
using System.Threading;
using System.Threading.Tasks;

namespace PENet
{
    public enum SessionState
    {
        None,
        Connected,
        DisConnected
    }
    public abstract class KCPSession<TMsg> where TMsg : KCPMsg, new()
    {
        protected uint m_sid;

        Action<byte[], IPEndPoint> m_udpSender;
        protected abstract void OnReciveMsg(TMsg msg);

        private IPEndPoint m_remotePoint;
        protected SessionState m_sessionState = SessionState.None;

        public KCPHandle m_handle;
        public Kcp m_kcp;
        private CancellationTokenSource cts;
        private CancellationToken ct;

        public void InitSession(uint sid,Action<byte[],IPEndPoint> udpSender,IPEndPoint remotePoint)
        {
            m_sid = sid;
            m_udpSender = udpSender;
            m_remotePoint = remotePoint;
            m_sessionState = SessionState.Connected;

            m_handle = new KCPHandle();
            m_kcp = new Kcp(sid, m_handle);
            m_kcp.NoDelay(1, 10, 2, 1);
            m_kcp.WndSize(64, 64);
            m_kcp.SetMtu(512);

            m_handle.Out = (buffer) =>
            {
                byte[] bytes = buffer.ToArray();
                m_udpSender(bytes, m_remotePoint);
            };

            m_handle.Recv = (byte[] buffer) => {
                buffer = KCPTool.KCPDeCompress(buffer);
                TMsg msg = KCPTool.KCPDeSerialize<TMsg>(buffer);
                if (msg != null)
                {
                    OnReciveMsg(msg);
                }
            };

            OnConnected();

            cts = new CancellationTokenSource();
            ct = cts.Token;
            Task.Run(Update, ct);
        }

        async void Update()
        {
            try
            {
                while(true)
                {
                    DateTime now = DateTime.UtcNow;
                    OnUpdate(now);
                    if(ct.IsCancellationRequested)
                    {
                        KCPTool.ColorLog(KCPLogColor.Cyan, "SessionUpdate Task is Cancelled.");
                        break;
                    }
                    m_kcp.Update(now);
                    //处理udp收到后KCP处理完后的数据
                    int len;
                    while((len = m_kcp.PeekSize())>0)
                    {
                        var buffer = new byte[len];
                        if (m_kcp.Recv(buffer) >= 0)
                            m_handle.Recive(buffer);
                    }
                }
            }
            catch (Exception e)
            {
                KCPTool.Warn("Session Update Exception:{0}", e.ToString());
            }
        }

        public void ReciveData(byte[] buffer)
        {
            m_kcp.Input(buffer.AsSpan());
        }

        public void SendMsg(TMsg msg)
        {
            if(IsConnected)
            {
                byte[] bytes = KCPTool.KCPSerialize(msg);
                if(bytes != null)
                {
                    SendMsg(bytes);
                }
            }
            else
                KCPTool.Warn("Session Disconnected.Can not send msg.");
        }

        public void SendMsg(byte[] msg)
        {
            if (IsConnected)
            {
                msg = KCPTool.KCPCompress(msg);
                m_kcp.Send(msg.AsSpan());
            }
            else
                KCPTool.Warn("Session Disconnected.Can not send msg.");
        }

        public void CloseSession()
        {
            cts.Cancel();
            OnDisConnected();

            OnSessionClose?.Invoke(m_sid);
            OnSessionClose = null;

            m_sessionState = SessionState.DisConnected;
            m_remotePoint = null;
            m_udpSender = null;
            m_sid = 0;

            m_handle = null;
            m_kcp = null;
            cts = null;
        }

        public bool IsConnected => m_sessionState == SessionState.Connected;

        public Action<uint> OnSessionClose;
        protected abstract void OnUpdate(DateTime now);
        protected abstract void OnConnected();
        protected abstract void OnDisConnected();

        public override bool Equals(object obj)
        {
            if (obj is KCPSession<TMsg>)
            {
                KCPSession<TMsg> us = obj as KCPSession<TMsg>;
                return m_sid == us.m_sid;
            }
            return false;
        }
        public override int GetHashCode()
        {
            return m_sid.GetHashCode();
        }
        public uint GetSessionID()
        {
            return m_sid;
        }
    }
}
